package main;

import java.util.ArrayList;
import java.util.Map;
import java.util.Random;

import problemSpace.ConcretePlanning;
import problemSpace.Order;
import problemSpace.Vehicle;

public class SimulatedAnnealing {
	
	static Random rand = new Random();

	public static ConcretePlanning simulatedAnnealing(long time, int tMax, int tMin,
			int loops, double alpha, int maxRandom) {
		
		ConcretePlanning s = InitialSolution.createInitial();
		double scoreOld = Evaluation.calcScore(s, false);
		ConcretePlanning besteOplossing = new ConcretePlanning(s);
		double besteScore = Evaluation.calcScore(s, false);
		Random rand = new Random();
		long begin = System.currentTimeMillis();
		int T = tMax;
		double scoreNew = 0;
		double comp,value;
		double energy = 0;
		
		ConcretePlanning sNew;
		do {
			int loop = loops;
			System.out.println("\n"+"Temperature " + T);
			do {
				sNew = new ConcretePlanning(s);
				//randomNeighboor(sNew, 1, losTeMaken);
				//if(T>tMax*3/5)
				//	sNew = rescheduleWorstAndRandomOrderMultipleTimes(sNew, 15, 3);
				if(T>tMax*1/5)
					sNew = rescheduleWorstAndRandomOrderMultipleTimes(sNew, 15, 2);
				if(T>0)
					sNew = rescheduleWorstAndRandomOrderMultipleTimes(sNew, 5, 1);
				
				scoreNew = Evaluation.calcScore(sNew, false);
				
				if(scoreNew<besteScore){
					besteOplossing = new ConcretePlanning(sNew);
					besteScore = scoreNew;
				}
				energy = scoreNew - scoreOld;
				if(scoreNew>Evaluation.HARDCONSTRPENALTY) System.out.print(".");
//				System.out.println("Score old "+scoreOld+", score new "+scoreNew+", energy "+energy);
				if(scoreNew>0)
					if (energy < 0) {
						s = new ConcretePlanning(sNew);
						scoreOld = scoreNew;
						System.out.println("Temperature " + T + ": Better solution accepted, score " + scoreNew + ", energy " + energy);
					} else if (energy != 0){
						comp = rand.nextInt(maxRandom)+1;
						value = Math.exp(-energy / T) * 50;
						if (value > comp) {
							s = new ConcretePlanning(sNew);
							scoreOld = scoreNew;
							double prob = Math.floor(value/maxRandom*10000)/100; // Probability afgerond op 0,01
							System.out.println("Temperature " + T + ": Worse solution accepted,  score " + scoreNew + ", energy +" + energy+" (probability: "+prob+" %)");
						} //else if(scoreNew<Evaluation.HARDCONSTRPENALTY) System.out.print("o");
					}
				sNew=null;
				loop--;
			} while (loop > 0);

			// Lower temperature
			//T = (int) (T * alpha);
			T -=tMax/25;
			if(T<=tMax/25)
				T=tMax;
		} while ((System.currentTimeMillis() - begin) < time);
		System.out.println(besteScore);
		return besteOplossing;
	}
	
	public static ConcretePlanning rescheduleVehicles(ConcretePlanning s, int times){
		double bestScore = Double.MAX_VALUE;
		ConcretePlanning bestPlanning=null;
		for(Vehicle v : s.vehicles){
			ConcretePlanning sNew = new ConcretePlanning(s);
			HelperFunctions.undoVehicle(sNew,v);
			HelperFunctions.rescheduleOrders(sNew);
			if(Evaluation.calcScore(sNew, false)<bestScore){
				bestScore = Evaluation.calcScore(s, false);
				bestPlanning = new ConcretePlanning(sNew);
			}
		}
		return bestPlanning;
	}
	public static ConcretePlanning rescheduleWorstAndRandomOrderMultipleTimes(ConcretePlanning s,int times,int numberOfOrders){
		Map<Order, Double> sortedOrders = Evaluation.evaluateOrdersAndSort(s);
		ArrayList<Integer> randoms = new ArrayList<Integer>();
		ArrayList<Order> orders = new ArrayList<Order>();
		double bestScore = Double.MAX_VALUE;
		ConcretePlanning bestPlanning=null;
		
		// Er worden X orders losgemaakt en terug gescheduled (X = parameter numberOfOrders)
		int r = 0;
		randoms.add(r);
		while(randoms.size()<numberOfOrders){
			r=rand.nextInt(sortedOrders.size()-1);
			if(!randoms.contains(r)){
				randoms.add(r);
			}
		}
		// Orders toevoegen aan lijst
		for(int i = 0 ; i<randoms.size() ; i++){
			orders.add(Evaluation.findWorst(sortedOrders, randoms.get(i)));
		}
		
		for(int count=times;count>0;count--){
			// Orders herinitialiseren
			for(int i = 0 ; i<orders.size() ; i++){
				orders.set(i, HelperFunctions.undoOrder(s, orders.get(i)));
			}
			
			// Orders verwijderen
			for(int i= s.orders.size()-1;i>=0;i--){
				Order o = s.orders.get(i);
				for(Order o2 : orders){
					if(o.orderCode.equals(o2.orderCode))
						s.orders.remove(i);
				}
			}
			
			// Orders in de gewenste volgorde terugzetten
			for(int i = 0 ; i<orders.size() ; i++){
				s.orders.add(orders.get(i));
			}
			
			// Orders terugschedulen
			HelperFunctions.rescheduleOrders(s);
			
			// Beste neighboor bijhouden
			if(Evaluation.calcScore(s, false)<bestScore){
				bestScore = Evaluation.calcScore(s, false);
				bestPlanning = new ConcretePlanning(s);
			}
		}
		return bestPlanning;
	}
}
